/*************************************************************************
 *
 *  Copyright (C) 2013 Florian Dang <florian.coin@gmail.com>
 *
 *  This file is part of NatusVerse.
 *
 *  NatusVerse is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  NatusVerse is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NatusVerse.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/

#include <iostream>
using std::cout;
using std::endl;
using std::cerr;

#include <cstdio>
#include <cstdlib>
#include <string>
using std::string;

#ifdef INTEL_TBB
# include <tbb/task_scheduler_init.h>
# include <tbb/parallel_for.h>
# include <tbb/blocked_range.h>
#else
# ifdef _OPENMP
#  include <omp.h>
# endif
#endif

#ifdef _MPI
#include <mpi.h>
#endif

#include "config.h"
#include "perfs.h"
#include "utils.h"

#include "image/image.h"
#include "image/fractal.h"
#include "sound/sound.h"
#include "graphics/graphics.h"
#include "nbody/universe.h"
#include "life/life.h"

#include "raytracer/raytrace.h"

typedef struct s_program
{
	bool bool_gol;
	bool bool_rt;
	bool bool_nbody;
	bool bool_fractal;
} t_program;

int  CheckOpt(int argc, char **argv, int &num_procs, int &num_particles,
             int &x, int &y, float &scale, float &radius, int &max_it,
             float &color, t_program& program);

void HandleOpenMP(int num_threads);


int CheckOpt(	int argc, char **argv, int &num_procs, int &num_particles,
				int &x, int &y, float &scale, float &radius, int &max_it,
				float &color, t_program& program)
{
	for (int i = 0; i < argc; i++)
	{
		if (!string("-h").compare(argv[i]) || !string("-help").compare(argv[i]))
		{
			cout << usage << endl;
			return 1;
		}
		if (!string("-c").compare(argv[i]))
			num_procs = atoi(argv[++i]);
		if (!string("-n").compare(argv[i]))
			num_particles = atoi(argv[++i]);
		if (!string("-x").compare(argv[i]))
			x = atoi(argv[++i]);
		if (!string("-y").compare(argv[i]))
			y = atoi(argv[++i]);
		if (!string("-scale").compare(argv[i]))
			scale = atof(argv[++i]);
		if (!string("-radius").compare(argv[i]))
			radius = atof(argv[++i]);
		if (!string("-it").compare(argv[i]))
			max_it = atoi(argv[++i]);
		if (!string("-color").compare(argv[i]))
			color = atof(argv[++i]);
		if (!string("-rt").compare(argv[i]))
			program.bool_rt = true;
		if (!string("-gol").compare(argv[i]))
			program.bool_gol = true;
		if (!string("-nbody").compare(argv[i]))
			program.bool_nbody = true;
	}

	return EXIT_SUCCESS;
}

void HandleOpenMP(int num_threads)
{
#ifdef _OPENMP
	//omp_set_nested(true);	// Nesting parallelism enabled
	omp_set_num_threads(num_threads);
	#pragma omp parallel
	{
	#pragma omp master
	{
		printf("(i) %d threads used on %d available procs.\n", omp_get_num_threads(),
				omp_get_num_procs());
		printf("(i) nested parallelism is ");
		omp_get_nested() == 1 ? printf("ON.\n") : printf("OFF.\n");
	}
	}
#endif
}

int main(int argc, char **argv)
{
	int num_procs = 1; // Monocore by default
	t_program program;
	program.bool_gol = program.bool_rt = program.bool_nbody = false;

	cout << "NatusVerse v" << VERSION_MAJOR << "."
	<< VERSION_MINOR << endl;

	int	w, h;
	w = default_width;
	h = default_height;

	// Mandelbrot
	int x, y, max_it;
	float	scale, radius, color;
	x = w/2; y = h/2;
	scale = 1.0f;
	radius = 2.0f;
	max_it = 800;
	color = 20.0f;

	// NBody
	int num_particles = 8;

#ifdef _OPENMP
	num_procs = omp_get_num_procs();	// Maximum available cores used by default
#endif

	if (CheckOpt(argc, argv, num_procs, num_particles,
				 x, y, scale, radius, max_it,
				 color, program) != 0)
		return EXIT_SUCCESS;


	HandleOpenMP(num_procs);

#if 0
	cout << "*** Mandelbrot ***" << endl;
	Fractal::CallMandelbrot(w, h, x, y, scale, radius, max_it, color, "mandel");
	cout << "*** Sound Generator ***" << endl;
	Sound::SoundGenerator();
	Sound::SoundRandom();
#endif

	if (program.bool_gol == true)
	{
		cout << "*** Game of Life ***" << endl;
		graphics::Graphics win_life(450, 450, false, false);
		life::Life conway_game(win_life);
		conway_game.GameOfLife(150, 150);
	}

	if (program.bool_rt == true)
	{
		cout << "*** Raytracer ***" << endl;

		graphics::Graphics win_rt(1024, 768, false, false);
		raytracer::Raytrace raytrace(win_rt);
		raytrace.Raytracing();
	}

	if (program.bool_nbody == true)
	{
		cout << "*** NBody Simulation ***" << endl;
		graphics::Graphics win_nbody(w, h, false, false);
		nbody::Universe    universe(win_nbody, num_particles);
		universe.Simulation(0.4f);
	}

	return EXIT_SUCCESS;
}
